#include "LspManagement.h"
#include "MainWindow.h"
#include "DocumentWindow.h"
#include "QRpc.h"
#include "LspBuilders.h"

#include "simodo/shell/document/ViewAdaptorLsp_interface.h"

#include "simodo/lsp-client/Location.h"
#include "simodo/lsp-client/SimodoCommandResult.h"

#include <QCoreApplication>
#include <QJsonObject>
#include <QJsonArray>

#include <QMessageBox>

using namespace simodo;
using namespace simodo::inout;

LspManagement::LspManagement(MainWindow * main_window)
    : _main_window(main_window)
    , _log(main_window,"LSP-client",shell::MessageSeverity::Info)
{
    connect(this, &LspManagement::publishDiagnostics_notify_signal, this, &LspManagement::publishDiagnostics_notify_slot);
    connect(this, &LspManagement::hover_result_signal, this, &LspManagement::hover_result_slot);
    connect(this, &LspManagement::documentSymbol_result_signal, this, &LspManagement::documentSymbol_result_slot);
    connect(this, &LspManagement::semanticTokens_result_signal, this, &LspManagement::semanticTokens_result_slot);
    connect(this, &LspManagement::declaration_result_signal, this, &LspManagement::declaration_definition_result_slot);
    connect(this, &LspManagement::definition_result_signal, this, &LspManagement::declaration_definition_result_slot);
    connect(this, &LspManagement::completion_result_signal, this, &LspManagement::completion_result_slot);
    connect(this, &LspManagement::simodo_command_result_signal, this, &LspManagement::simodo_command_result_slot);
}

bool LspManagement::prepare()
{
    QDir dir(QCoreApplication::applicationDirPath()+"/../data/shell/lsp");
    if (!dir.exists()) {
        _main_window->sendGlobal(tr("The LSP server configuration directory '%1' does not exist")
                                 .arg(dir.absolutePath()),
                                 shell::MessageSeverity::Error);
        return false;
    }

    dir.setFilter(QDir::Files | QDir::NoSymLinks);
    dir.setNameFilters({"*.json", "*.json5"});
    for(QFileInfo fi : dir.entryInfoList()) {
        prepareServer(fi.absoluteFilePath());
    }

    return true;
}

bool LspManagement::checkPath(const QString &path)
{
    QFileInfo fi(path);

    auto it_extension = _extension_to_language_and_server_name.find(fi.suffix());
    if (it_extension == _extension_to_language_and_server_name.end())
        return false;

    auto & [grammar_name,server_config_path] = it_extension->second;
    auto it_server = _servers.find(server_config_path);
    return (it_server != _servers.end());
}

void LspManagement::opened(const QString & path_to_file, const std::shared_ptr<shell::DocumentAdaptor_interface> doc)
{
    if (!doc) {
        _main_window->sendGlobal(QObject::tr("Null doc adaptor received for '%1'").arg(path_to_file));
        return;
    }

    QFileInfo fi(path_to_file);
    auto it_extension = _extension_to_language_and_server_name.find(fi.suffix());
    if (it_extension == _extension_to_language_and_server_name.end()) {
        return;
    }

    auto & [grammar_name,server_config_path] = it_extension->second;
    auto it_server = _servers.find(server_config_path);
    if (it_server == _servers.end()) {
        if (!prepareServer(server_config_path,true))
            return;
        it_server = _servers.find(server_config_path);
        if (it_server == _servers.end())
            return;
    }

    std::u16string uri              = fi.canonicalFilePath().toStdU16String();
    std::u16string grammar_name_u16 = grammar_name.toStdU16String();
    std::u16string text_u16         = doc->text().toStdU16String();

    _tp.submit( [it_server, uri, grammar_name_u16, text_u16]{
                    it_server->second->exec("textDocument/didOpen",
                                            variable::Object {{
                                                {u"textDocument", variable::Object {{
                                                    {u"uri",        uri},
                                                    {u"languageId", grammar_name_u16},
                                                    {u"version",    0},
                                                    {u"text",       encodeSpecialChars(text_u16)},
                                                }}},
                                            }});
    });

    _main_window->sendGlobal(QObject::tr("LSP: 'didOpen' notification for '%1' sent to server").arg(path_to_file),
                             shell::MessageSeverity::Debug);
}

void LspManagement::closed(const QString & path_to_file)
{
    QFileInfo fi(path_to_file);

    auto it_extension = _extension_to_language_and_server_name.find(fi.suffix());
    if (it_extension == _extension_to_language_and_server_name.end())
        return;

    auto & [grammar_name,server_config_path] = it_extension->second;
    auto it_server = _servers.find(server_config_path);
    if (it_server == _servers.end())
        return;

    std::u16string uri = fi.canonicalFilePath().toStdU16String();

    it_server->second->exec("textDocument/didClose",
                            variable::Object {{
                                {u"textDocument", variable::Object {{
                                    {u"uri",      uri},
                                }}},
                            }});

    _main_window->sendGlobal(QObject::tr("LSP: 'didClose' notification for '%1' sent to server").arg(path_to_file),
                             shell::MessageSeverity::Debug);
}

QString LspManagement::getLanguageId(const QString & path)
{
    QString extension      = QFileInfo(path).suffix();
    auto    it_server_name = _extension_to_language_and_server_name.find(extension);

    if (it_server_name == _extension_to_language_and_server_name.end())
        return "";

    return it_server_name->first;
}

bool LspManagement::checkServerCapability(const QString & path, shell::ServerCapability capability)
{
    QString extension      = QFileInfo(path).suffix();
    auto    it_server_name = _extension_to_language_and_server_name.find(extension);

    if (it_server_name == _extension_to_language_and_server_name.end())
        return false;

    auto it_server = _servers.find(it_server_name->second.second);
    if (it_server == _servers.end())
        return false;

    const lsp::ServerCapabilities & capabilities = it_server->second->server_capabilities();

    switch(capability)
    {
    case shell::ServerCapability::hover:
        return capabilities.hoverProvider;
    case shell::ServerCapability::documentSymbol:
        return capabilities.documentSymbolProvider;
    case shell::ServerCapability::semanticTokens:
        return !capabilities.semanticTokensProvider.legend.tokenTypes.empty();
    case shell::ServerCapability::declaration:
        return capabilities.declarationProvider;
    case shell::ServerCapability::definition:
        return capabilities.definitionProvider;
    case shell::ServerCapability::completion:
        return !capabilities.completionProvider.triggerCharacters.empty();
    default:
        break;
    }

    return false;
}

lsp::ServerCapabilities LspManagement::getServerCapabilities(const QString & path)
{
    static lsp::ServerCapabilities dummy;

    QString extension      = QFileInfo(path).suffix();
    auto    it_server_name = _extension_to_language_and_server_name.find(extension);

    if (it_server_name == _extension_to_language_and_server_name.end())
        return dummy;

    auto it_server = _servers.find(it_server_name->second.second);
    if (it_server == _servers.end())
        return dummy;

    return it_server->second->server_capabilities();
}

simodo::lsp::SimodoCapabilities LspManagement::getSimodoCapabilities(const QString& path)
{
    static lsp::SimodoCapabilities dummy;

    QString extension      = QFileInfo(path).suffix();
    auto    it_server_name = _extension_to_language_and_server_name.find(extension);

    if (it_server_name == _extension_to_language_and_server_name.end())
        return dummy;

    auto it_server = _servers.find(it_server_name->second.second);
    if (it_server == _servers.end())
        return dummy;

    return it_server->second->simodo_capabilities();
}

void LspManagement::didChange(const QString & path, int version)
{
    QString extension      = QFileInfo(path).suffix();
    auto    it_server_name = _extension_to_language_and_server_name.find(extension);

    if (it_server_name == _extension_to_language_and_server_name.end())
        return;

    auto it_server = _servers.find(it_server_name->second.second);
    if (it_server == _servers.end())
        return;

    DocumentWindow * doc_win = _main_window->findDocumentWindow(path);
    if (doc_win == nullptr)
        return;

    std::shared_ptr<shell::DocumentAdaptor_interface> doc = doc_win->document_adaptor();
    if (!doc)
        return;

    std::u16string uri      = QFileInfo(path).canonicalFilePath().toStdU16String();
    std::u16string text_u16 = doc->text().toStdU16String();

    _tp.submit( [it_server, uri, version, text_u16]{
                    it_server->second->exec("textDocument/didChange",
                                            variable::Object {{
                                                {u"textDocument", variable::Object {{
                                                    {u"uri",        uri},
                                                    {u"version",    version},
                                                }}},
                                                {u"contentChanges", variable::Array {{
                                                    variable::Object {{{u"text", encodeSpecialChars(text_u16)}}},
                                                }}},
                                            }});
    });

    _main_window->sendGlobal(QObject::tr("LSP: 'didChange' notification for '%1' sent to server").arg(path),
                             shell::MessageSeverity::Debug);
}

void LspManagement::hover(const void * sender, const QString & path, int line, int character)
{
    QFileInfo fi(path);

    auto it_extension = _extension_to_language_and_server_name.find(fi.suffix());
    if (it_extension == _extension_to_language_and_server_name.end())
        return;

    auto & [grammar_name,server_config_path] = it_extension->second;
    auto it_server = _servers.find(server_config_path);
    if (it_server == _servers.end())
        return;

    std::u16string uri = fi.canonicalFilePath().toStdU16String();

    it_server->second->exec("textDocument/hover",
                            variable::Object {{
                                {u"textDocument", variable::Object {{
                                    {u"uri", uri},
                                }}},
                                {u"position", variable::Object {{
                                    {u"line", line},
                                    {u"character", character},
                                }}},
                            }},
                            [this, uri](const variable::JsonRpc & rpc, const void * sender){
                                emit hover_result_signal(QString::fromStdU16String(uri), 
                                                QString::fromStdString(rpc.origin()), 
                                                sender);
                            },
                            sender);

    _main_window->sendGlobal(QObject::tr("LSP: 'hover' request for '%1' sent to server").arg(path),
                             shell::MessageSeverity::Debug);
}

void LspManagement::documentSymbol(const QString & path)
{
    QFileInfo fi(path);

    auto it_extension = _extension_to_language_and_server_name.find(fi.suffix());
    if (it_extension == _extension_to_language_and_server_name.end())
        return;

    auto & [grammar_name,server_config_path] = it_extension->second;
    auto it_server = _servers.find(server_config_path);
    if (it_server == _servers.end())
        return;

    std::u16string uri = fi.canonicalFilePath().toStdU16String();

    it_server->second->exec("textDocument/documentSymbol", 
                variable::Object {{
                    {u"textDocument", variable::Object {{
                        {u"uri", uri},
                    }}},
                }}, 
                [this, uri](const variable::JsonRpc & rpc, const void * ){
                    emit documentSymbol_result_signal(QString::fromStdU16String(uri), 
                                    QString::fromStdString(rpc.origin()));
                });

    _main_window->sendGlobal(QObject::tr("LSP: 'documentSymbol' request for '%1' sent to server").arg(path),
                             shell::MessageSeverity::Debug);
}

void LspManagement::semanticTokens(const QString & path)
{
    QFileInfo fi(path);

    auto it_extension = _extension_to_language_and_server_name.find(fi.suffix());
    if (it_extension == _extension_to_language_and_server_name.end())
        return;

    auto & [grammar_name,server_config_path] = it_extension->second;
    auto it_server = _servers.find(server_config_path);
    if (it_server == _servers.end())
        return;

    std::u16string uri = fi.canonicalFilePath().toStdU16String();

    it_server->second->exec("textDocument/semanticTokens/full", 
                variable::Object {{
                    {u"textDocument", variable::Object {{
                        {u"uri", uri},
                    }}},
                }}, 
                [this, uri](const variable::JsonRpc & rpc, const void *){
                    emit semanticTokens_result_signal(QString::fromStdU16String(uri), 
                                    QString::fromStdString(rpc.origin()));
                });
    _main_window->sendGlobal(QObject::tr("LSP: 'semanticTokens' request for '%1' sent to server").arg(path),
                             shell::MessageSeverity::Debug);
}

void LspManagement::declaration(const QString & path, int line, int character)
{
    QFileInfo fi(path);

    auto it_extension = _extension_to_language_and_server_name.find(fi.suffix());
    if (it_extension == _extension_to_language_and_server_name.end())
        return;

    auto & [grammar_name,server_config_path] = it_extension->second;
    auto it_server = _servers.find(server_config_path);
    if (it_server == _servers.end())
        return;

    std::u16string uri = fi.canonicalFilePath().toStdU16String();

    it_server->second->exec("textDocument/declaration",
                            variable::Object {{
                                {u"textDocument", variable::Object {{
                                    {u"uri", uri},
                                }}},
                                {u"position", variable::Object {{
                                    {u"line", line},
                                    {u"character", character},
                                }}},
                            }},
                            [this, uri](const variable::JsonRpc & rpc, const void * ){
                                emit declaration_result_signal(QString::fromStdU16String(uri), 
                                                QString::fromStdString(rpc.origin()));
                            });

    _main_window->sendGlobal(QObject::tr("LSP: 'declaration' request for '%1' sent to server").arg(path),
                             shell::MessageSeverity::Debug);
}

void LspManagement::definition(const QString & path, int line, int character)
{
    QFileInfo fi(path);

    auto it_extension = _extension_to_language_and_server_name.find(fi.suffix());
    if (it_extension == _extension_to_language_and_server_name.end())
        return;

    auto & [grammar_name,server_config_path] = it_extension->second;
    auto it_server = _servers.find(server_config_path);
    if (it_server == _servers.end())
        return;

    std::u16string uri = fi.canonicalFilePath().toStdU16String();

    it_server->second->exec("textDocument/definition",
                            variable::Object {{
                                {u"textDocument", variable::Object {{
                                    {u"uri", uri},
                                }}},
                                {u"position", variable::Object {{
                                    {u"line", line},
                                    {u"character", character},
                                }}},
                            }},
                            [this, uri](const variable::JsonRpc & rpc, const void * ){
                                emit definition_result_signal(QString::fromStdU16String(uri), 
                                                QString::fromStdString(rpc.origin()));
                            });

    _main_window->sendGlobal(QObject::tr("LSP: 'definition' request for '%1' sent to server").arg(path),
                             shell::MessageSeverity::Debug);
}

void LspManagement::completion(const void * sender, const QString &path, int line, int character, 
                               simodo::lsp::CompletionTriggerKind trigger_kind, 
                               const QString &trigger_character)
{
    QFileInfo fi(path);

    auto it_extension = _extension_to_language_and_server_name.find(fi.suffix());
    if (it_extension == _extension_to_language_and_server_name.end())
        return;

    auto & [grammar_name,server_config_path] = it_extension->second;
    auto it_server = _servers.find(server_config_path);
    if (it_server == _servers.end())
        return;

    std::u16string uri = fi.canonicalFilePath().toStdU16String();

    it_server->second->exec("textDocument/completion",
                            variable::Object {{
                                {u"textDocument", variable::Object {{
                                    {u"uri", uri},
                                }}},
                                {u"position", variable::Object {{
                                    {u"line", line},
                                    {u"character", character},
                                }}},
                                {u"context", variable::Object {{
                                    {u"triggerKind", static_cast<int64_t>(trigger_kind)},
                                    {u"triggerCharacter", trigger_character.toStdU16String()},
                                }}},
                            }},
                            [this, uri](const variable::JsonRpc & rpc, const void * sender){
                                emit completion_result_signal(QString::fromStdU16String(uri), 
                                            QString::fromStdString(rpc.origin()), 
                                            sender);
                            },
                            sender);

    _main_window->sendGlobal(QObject::tr("LSP: 'completion' request for '%1' sent to server").arg(path),
                             shell::MessageSeverity::Debug);
}

void LspManagement::executeSimodoCommand(const QString & path, const QString & command)
{
    QFileInfo fi(path);

    auto it_extension = _extension_to_language_and_server_name.find(fi.suffix());
    if (it_extension == _extension_to_language_and_server_name.end())
        return;

    auto & [grammar_name,server_config_path] = it_extension->second;
    auto it_server = _servers.find(server_config_path);
    if (it_server == _servers.end())
        return;

    std::u16string uri = fi.canonicalFilePath().toStdU16String();

    it_server->second->exec("simodo/command",
                            variable::Object {{
                                {u"textDocument", variable::Object {{
                                    {u"uri", path.toStdU16String()},
                                }}},
                                {u"command", command.toStdU16String()},
                            }},
                            [this, uri](const variable::JsonRpc & rpc, const void * ){
                                emit simodo_command_result_signal(QString::fromStdU16String(uri), QString::fromStdString(rpc.origin()));
                            });

    _main_window->sendGlobal(QObject::tr("LSP: 'simodo/command' request for '%1' sent to server").arg(path),
                             shell::MessageSeverity::Debug);
}

// ------------------------------------------------------------------------------------------
// private slots ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------

void LspManagement::publishDiagnostics_notify_slot(QString jsonrpc)
{
    QJsonRpc rpc(jsonrpc);
    if (!rpc.is_valid() || !rpc.error().isNull()) {
        _log.error("Unable to get 'publishDiagnostics' result", jsonrpc.toStdString());
        return;
    }
    if (!rpc.params().isObject()) {
        _log.error("Wrong params structure in notification", jsonrpc.toStdString());
        return;
    }
    QJsonObject params_object    = rpc.params().toObject();

    QJsonValue uri_value         = params_object["uri"];
    QJsonValue version_value     = params_object["version"];
    QJsonValue diagnostics_value = params_object["diagnostics"];

    if (!uri_value.isString() || !version_value.isDouble() || !diagnostics_value.isArray()) {
        _log.error("Wrong params structure in notification", jsonrpc.toStdString());
        return;
    }

    QString                     uri         = uri_value.toString();
    int                         version     = version_value.toInt();
    int                         checksum    = 0;
    QVector<shell::Diagnostic>  diagnostics = builtDiagnostics(diagnostics_value.toArray(),checksum);

    QFileInfo   fi   { uri };
    std::string path { fi.absoluteFilePath().toStdString() };

    DocumentWindow * document_window = _main_window->findDocumentWindow(QString::fromStdString(path));
    if (document_window) {
        shell::ViewAdaptorLsp_interface * lsp_callback = document_window->view_adaptor()->getLspInterface();
        if (lsp_callback && lsp_callback->document_version() == version) {
            document_window->publishDiagnostics(version, diagnostics, checksum);
            lsp_callback->publishDiagnostics(version, diagnostics, checksum);
        }
    }
}

void LspManagement::hover_result_slot(QString path, QString jsonrpc, const void * sender)
{
    QJsonRpc rpc(jsonrpc);
    if (!rpc.is_valid() || !rpc.error().isNull()) {
        _log.error("Unable to get 'hover' result", jsonrpc.toStdString());
        return;
    }
    if (!rpc.result().isNull() && !rpc.result().isObject()) {
        _log.error("Wrong params structure in notification", jsonrpc.toStdString());
        return;
    }

    QString kind, value;

    if (rpc.result().isObject()) {
        QJsonValue contents_value   = rpc.result().toObject()["contents"];
        // QJsonValue range_value      = rpc.result().toObject()["range"];
        if (!contents_value.isObject() /*|| !range_value.isObject()*/) {
            _log.error("Wrong params structure in notification", jsonrpc.toStdString());
            return;
        }

        QJsonValue kind_value   = contents_value.toObject()["kind"];
        QJsonValue value_value   = contents_value.toObject()["value"];
        if (!kind_value.isString() || !value_value.isString()) {
            _log.error("Wrong params structure in notification", jsonrpc.toStdString());
            return;
        }
        kind  = kind_value.toString();
        value = value_value.toString().replace("&NewLine;","\n").replace("&QUOT;","\"");
    }

    DocumentWindow * document_window = _main_window->findDocumentWindow(QFileInfo(path).absoluteFilePath());
    if (document_window) {
        shell::ViewAdaptorLsp_interface * lsp_callback = document_window->view_adaptor()->getLspInterface();
        if (lsp_callback)
            lsp_callback->hover(sender, kind, value);
    }
}

void LspManagement::documentSymbol_result_slot(QString path, QString jsonrpc)
{
    QJsonRpc rpc(jsonrpc);
    if (!rpc.is_valid() || !rpc.error().isNull()) {
        _log.error("Unable to get 'documentSymbol' result", jsonrpc.toStdString());
        return;
    }
    if (!rpc.result().isNull() && !rpc.result().isArray()) {
        _log.error("Wrong params structure in 'documentSymbol' result", jsonrpc.toStdString());
        return;
    }

    QVector<shell::DocumentSymbol> symbols;

    QJsonArray symbols_array = rpc.result().toArray();

    for(auto symbol_value : symbols_array)
        if (symbol_value.isObject()) {
            QJsonObject symbol_object = symbol_value.toObject();
            auto name_value           =  symbol_object["name"];
            auto detail_value         =  symbol_object["detail"];
            auto kind_value           =  symbol_object["kind"];
            auto range_value          =  symbol_object["range"];
            auto selectionRange_value =  symbol_object["selectionRange"];

            if (!name_value.isString() || !kind_value.isDouble()
             || !range_value.isObject() || !selectionRange_value.isObject())
                continue;

            QString name              = name_value.toString();
            QString detail            = detail_value.isString() ? detail_value.toString() : "";
            lsp::SymbolKind kind      = static_cast<lsp::SymbolKind>(kind_value.toInt());
            lsp::Range range          = builtRange(range_value.toObject());
            lsp::Range selectionRange = builtRange(selectionRange_value.toObject());

            symbols.push_back({name, detail, kind, range, selectionRange});
        }

    DocumentWindow * document_window = _main_window->findDocumentWindow(QFileInfo(path).absoluteFilePath());
    if (document_window) {
        shell::ViewAdaptorLsp_interface * lsp_callback = document_window->view_adaptor()->getLspInterface();
        if (lsp_callback)
            lsp_callback->documentSymbol(symbols);
    }
}

void LspManagement::semanticTokens_result_slot(QString path, QString jsonrpc)
{
    QJsonRpc rpc(jsonrpc);
    if (!rpc.is_valid() || !rpc.error().isNull()) {
        _log.error("Unable to get 'semanticTokens' result", jsonrpc.toStdString());
        return;
    }
    if (!rpc.result().isNull() && !rpc.result().isObject()) {
        _log.error("Wrong params structure in 'semanticTokens' result", jsonrpc.toStdString());
        return;
    }

    if (rpc.result().isNull())
        return;

    const QJsonValue & data_value = rpc.result().toObject()["data"];
    if (!data_value.isArray()) {
        _log.error("Wrong params structure in 'semanticTokens' result (!data_value.isArray())", 
                   jsonrpc.toStdString());
        return;
    }

    QFileInfo fi(path);

    auto it_extension = _extension_to_language_and_server_name.find(fi.suffix());
    if (it_extension == _extension_to_language_and_server_name.end())
        return;

    auto & [grammar_name,server_config_path] = it_extension->second;
    auto it_server = _servers.find(server_config_path);
    if (it_server == _servers.end())
        return;

    const lsp::SemanticTokensLegend & legend = it_server->second->server_capabilities().semanticTokensProvider.legend;

    std::multimap<int,shell::SemanticToken> tokens;

    QJsonArray data_array = data_value.toArray();
    int        checksum   = 0;

    for(int i=0; i+5 <= data_array.size(); i+=5) {
        int line                    = data_array[i].toInt();
        int startChar               = data_array[i+1].toInt();
        int length                  = data_array[i+2].toInt();
        int tokenType_index         = data_array[i+3].toInt();
        int tokenModifiers_flags    = data_array[i+4].toInt();

        std::u16string tokenType;
        if (tokenType_index >= 0 && static_cast<size_t>(tokenType_index) < legend.tokenTypes.size())
            tokenType = legend.tokenTypes[tokenType_index];
            
        std::vector<std::u16string> tokenModifiers;

        for(size_t s = 0; s < legend.tokenModifiers.size(); ++s) 
            if ((tokenModifiers_flags >> s) & 0x00000001)
                tokenModifiers.push_back(legend.tokenModifiers[s]);
        
        if (!tokenType.empty()) {
            tokens.insert({line, {line, startChar, length, tokenType, tokenModifiers}});
            checksum += line + startChar + length + tokenType_index + tokenModifiers_flags;
        }
    }

    DocumentWindow * document_window = _main_window->findDocumentWindow(fi.absoluteFilePath());
    if (document_window) {
        shell::ViewAdaptorLsp_interface * lsp_callback = document_window->view_adaptor()->getLspInterface();
        if (lsp_callback)
            lsp_callback->semanticTokens(tokens, checksum);
    }
}

void LspManagement::declaration_definition_result_slot(QString , QString jsonrpc)
{
    variable::JsonRpc rpc(jsonrpc.toStdString());
    if (!rpc.is_valid() || !rpc.error().isNull()) {
        _log.error("Unable to get 'declaration'/'definition' result", jsonrpc.toStdString());
        return;
    }
    if (!rpc.result().isNull() && !rpc.result().isObject()) {
        _log.error("Wrong params structure in 'declaration'/'definition' result", jsonrpc.toStdString());
        return;
    }

    if (rpc.result().isNull())
        return;

    lsp::Location location;

    if (!lsp::parseLocation(rpc.result(),location))
        return;

    // _main_window->sendGlobal(QObject::tr("LSP: Go to '%1', line %2, col %3")
    //                     .arg(QString::fromStdString(location.uri))
    //                     .arg(location.range.start.line)
    //                     .arg(location.range.start.character));

    QFileInfo fi   { QString::fromStdString(location.uri()) };
    QString   path { fi.absoluteFilePath() };

    if (!_main_window->openFile(path))
        return;

    if (location.range() == simodo::inout::Range {{0,0},{0,0}})
        return;

    DocumentWindow * document_window = _main_window->findDocumentWindow(path);
    if (document_window)
        document_window->setLineCol({int(location.range().start().line()+1), int(location.range().start().character()+1)});
}

void LspManagement::completion_result_slot(QString path, QString jsonrpc, const void * sender)
{
    QJsonRpc rpc(jsonrpc);
    if (!rpc.is_valid() || !rpc.error().isNull()) {
        _log.error("Unable to get 'completion' result", jsonrpc.toStdString());
        return;
    }
    if (!rpc.result().isNull() && !rpc.result().isArray()) {
        _log.error("Wrong params structure in 'completion' result", jsonrpc.toStdString());
        _log.debug(std::string("isValid: ") + (rpc.is_valid() ? "true" : "false") 
                    + ", isNull: " + (rpc.result().isNull() ? "true" : "false") 
                    + ", isArray: " + (rpc.result().isArray() ? "true" : "false"));
        return;
    }

    if (rpc.result().isNull())
        return;

    QVector<shell::CompletionItem> completions;

    QJsonArray completion_array = rpc.result().toArray();

    for(auto item : completion_array)
        if (item.isObject()) {
            QJsonObject item_object = item.toObject();
            auto label_value        =  item_object["label"];
            auto labelDetails_value =  item_object["labelDetails"];
            auto kind_value         =  item_object["kind"];

            if (!label_value.isString() || !kind_value.isDouble())
                continue;

            QString label           = label_value.toString();
            QString detail          = "";
            QString description     = "";
            lsp::CompletionItemKind kind    = static_cast<lsp::CompletionItemKind>(kind_value.toInt());

            if (labelDetails_value.isObject()) {
                QJsonObject labelDetails_object = labelDetails_value.toObject();
                auto detail_value       =  labelDetails_object["detail"];
                auto description_value  =  labelDetails_object["description"];

                if (detail_value.isString()) detail = detail_value.toString();
                if (description_value.isString()) description = description_value.toString();
            }
            completions.push_back({label, {detail, description}, kind});
        }

    DocumentWindow * document_window = _main_window->findDocumentWindow(QFileInfo(path).absoluteFilePath());
    if (document_window) {
        shell::ViewAdaptorLsp_interface * lsp_callback = document_window->view_adaptor()->getLspInterface();
        if (lsp_callback)
            lsp_callback->completionItems(sender, completions);
    }
}

void LspManagement::simodo_command_result_slot(QString /*path*/, QString jsonrpc)
{
    variable::JsonRpc rpc(jsonrpc.toStdString());
    if (!rpc.is_valid() || !rpc.error().isNull()) {
        _log.error("Unable to get 'simodo/command' result", jsonrpc.toStdString());
        return;
    }
    if (!rpc.result().isNull() && !rpc.result().isObject() && !rpc.result().isArray()) {
        _log.error("Wrong params structure in 'simodo/command' result", jsonrpc.toStdString());
        return;
    }

    if (rpc.result().isNull())
        return;

    lsp::SimodoCommandResult result;

    if (!lsp::parseSimodoCommandResult(rpc.result(), result))
        return;

    _main_window->sendGlobal(QObject::tr("LSP: simodo/command '%1', views %2")
                        .arg(QString::fromStdU16String(result.uri))
                        .arg(result.commandResult.size())
                        );

    bool ok = _main_window->openReports(QString::fromStdU16String(result.uri), result.commandResult);

    if (!ok)
        _log.error("Unable to show 'simodo/command' result", jsonrpc.toStdString());
}

// ------------------------------------------------------------------------------------------
// private ----------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------

bool LspManagement::prepareServer(const QString & path_to_client_setup, bool and_start)
{
    variable::Value setup_value;

    std::string error_message = variable::loadJson(path_to_client_setup.toStdString(), setup_value);

    if (!error_message.empty()) {
        _main_window->sendGlobal(QString::fromStdString(error_message), shell::MessageSeverity::Error);
        return false;
    }

    if (setup_value.type() != variable::ValueType::Object) {
        _main_window->sendGlobal(QObject::tr("Wrong LSP client setup structure in '%1'").arg(path_to_client_setup),
                                 shell::MessageSeverity::Error);
        return false;
    }

    std::shared_ptr<variable::Object> setup_object = setup_value.getObject();

    const variable::Value & language_server_value   = setup_object->find(u"language_server");
    const variable::Value & extensions_value        = setup_object->find(u"extensions");
    const variable::Value & initialize_params_value = setup_object->find(u"initialize_params");

    if (language_server_value.type() != variable::ValueType::Object
     || extensions_value.type() != variable::ValueType::Object
     || initialize_params_value.type() != variable::ValueType::Object) {
        _main_window->sendGlobal(QObject::tr("Wrong LSP client setup structure in '%1'").arg(path_to_client_setup),
                                 shell::MessageSeverity::Error);
        return false;
    }

    std::shared_ptr<variable::Object>   language_server_object  = language_server_value.getObject();
    std::shared_ptr<variable::Object>   extensions_object       = extensions_value.getObject();
    std::shared_ptr<variable::Object>   initialize_params_object= initialize_params_value.getObject();

    if (!and_start) {
        for(const variable::Variable & extension_variable : extensions_object->variables()) {
            if (extension_variable.type() != variable::ValueType::String) {
                _main_window->sendGlobal(QObject::tr("Wrong LSP client setup structure in '%1'").arg(path_to_client_setup),
                                        shell::MessageSeverity::Error);
                return false;
            }
            if (_extension_to_language_and_server_name.find(QString::fromStdU16String(extension_variable.name()))
             != _extension_to_language_and_server_name.end())
                _main_window->sendGlobal(QObject::tr("Extension '%1' doubled").arg(extension_variable.name()),
                                        shell::MessageSeverity::Warning);
            else {
                QString grammar_name = QString::fromStdU16String(extension_variable.value().getString());
                _extension_to_language_and_server_name.insert({QString::fromStdU16String(extension_variable.name()),
                                                            {grammar_name,path_to_client_setup}});
                // _main_window->sendGlobal(QObject::tr("Extension '%1' loaded").arg(extension_variable.name()));
            }
        }
    }

    if (_servers.find(path_to_client_setup) != _servers.end()) {
        _main_window->sendGlobal(QObject::tr("LSP server '%1' already loaded").arg(path_to_client_setup),
                                shell::MessageSeverity::Warning);
        return true;
    }

    const variable::Value & language_server_exec_value  = language_server_object->find(u"exec");
    const variable::Value & language_server_args_value  = language_server_object->find(u"args");

    if (language_server_exec_value.type() != variable::ValueType::String
     || language_server_args_value.type() != variable::ValueType::Array) {
        _main_window->sendGlobal(QObject::tr("Wrong LSP client setup structure in '%1'").arg(path_to_client_setup),
                                 shell::MessageSeverity::Error);
        return false;
    }

    std::u16string                     language_server_exec        = language_server_exec_value.getString();
    std::shared_ptr<variable::Array>   language_server_args_array  = language_server_args_value.getArray();

    language_server_exec = _main_window->shell().performAllMacroSubstitutions(
                                            QString::fromStdU16String(language_server_exec)).toStdU16String();

    std::vector<std::string> language_server_args;
    for(const variable::Value & language_server_exec_value : language_server_args_array->values()) {
        if (language_server_exec_value.type() != variable::ValueType::String) {
            _main_window->sendGlobal(QObject::tr("Wrong LSP client setup structure in '%1'").arg(path_to_client_setup),
                                    shell::MessageSeverity::Error);
            return false;
        }
        std::u16string arg_string = language_server_exec_value.getString();
        arg_string = _main_window->shell().performAllMacroSubstitutions(
                                            QString::fromStdU16String(arg_string)).toStdU16String();
        language_server_args.push_back(toU8(arg_string));
    }

    auto [it,ok] = _servers.insert({path_to_client_setup,
                    std::make_unique<lsp::LanguageClient>(toU8(language_server_exec),
                                    language_server_args,
                                    initialize_params_object->copy(),
                                    _log)});

    if (!ok || !it->second->running()) {
        _main_window->sendGlobal(QObject::tr("Unable to start LSP server '%1'").arg(language_server_exec),
                                 shell::MessageSeverity::Error);
        return false;
    }

    it->second->registerListener("textDocument/publishDiagnostics", [this](variable::JsonRpc rpc, const void * ) {
        /// @todo Неоптимально много преобразований форматов из-за использования двух разных структур
        /// для работы с JSON: simodo::variable и Qt. Желательно перейти на использование какой-то одной.
        emit publishDiagnostics_notify_signal(QString::fromStdString(rpc.origin()));
    });

    _main_window->sendGlobal(QObject::tr("The LSP server '%1' was loaded").arg(path_to_client_setup));
    return true;
}